This is a draft version of a MISRA C++ 202x rule proposed for public review.
MISRA Rule 8.1.2
Category: Advisory
Analysis Type: Decidable,Single Translation Unit
Amplification
This rule applies to capture by value and capture by reference.
Rationale
Naming the variables captured by a lambda expression clarifies its dependencies. This allows variables captured by reference and pointers captured
by value to be more easily identified, helping to ensure that they are not dangling when the lambda is called.
This issue cannot occur for a transient lambda [1], so there is no need to explicitly capture its variables.
Example
void bar( double val, double min, double max )
{
auto const easedVal = [&]()
{
if ( val < min ) { return ( val + min ) / 2; }
if ( val > max ) { return ( val + max ) / 2; }
return val;
}(); // Compliant - called immediately
auto const ease = [&]()
{
if ( val < min ) { return ( val + min ) / 2; }
if ( val > max ) { return ( val + max ) / 2; }
return val;
}; // Non-compliant
ease(); // - not an immediate call
}
template< typename It, typename Func >
bool f1( It b, It e, Func f ) // f1 does not store f
{
for ( It it = b; it != e; ++it )
{
if ( f( *it ) ) // f is called
{
return true;
}
}
return false;
}
template< typename Cont, typename Func >
bool f2( Cont const & c, Func f ) // f2 does not store f
{
return f1( std::begin(c), std::end(c), f ); // f passed to non-storing function
}
void foo( std::vector< size_t > const & v, size_t i )
{
bool b1 = f1( v.cbegin(), v.cend(),
[&]( size_t elem ) { return elem == i; } ); // Compliant
bool b2 = f2( v,
[&]( size_t elem ) { return elem == i; } ); // Compliant
}
struct Speedometer
{
std::vector< std::function< void ( double ) > > observers;
template< typename Func >
void addObserver( Func f ) // addObserver stores f
{
observers.push_back( f ); // Copying f to the std::function
}
};
void process( std::function< Speedometer() > );
auto f3()
{
Speedometer s;
process( [&](){ return s; } ); // Non-compliant - conversion to
// std::function stores the lambda
return [=]() { return s; }; // Non-compliant - implicit capture
}
void addLoggers( Speedometer s, std::ostream & os )
{
s.addObserver( [&]( double speed ) // Non-compliant - implicit capture
{ os << speed; });
s.addObserver( [&os]( double speed ) // Compliant - explicit capture
{ os << speed; } );
s.addObserver( []( double speed ) // Compliant - no capture
{ std::cout << speed; } );
}
Glossary
[1] Transient lambda
A lambda is transient when:
- It is immediately invoked; or
- It is passed to a function that does not store it.
A function does not store a lambda when:
- The function is defined in the same translation unit as the lambda; and
- The lambda is only copied or moved when it is passed as an argument; and
- The function only calls the lambda and/or passes the lambda to another function that does not store it.
Copyright The MISRA Consortium Limited © 2023